package epg;

import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;

import org.antlr.runtime.TokenStream;
import org.antlr.runtime.tree.Tree;

public class CodeGenerator {

	StringBuilder builder; // здесь храним промежуточный код
	Table table = new Table();
	private PrintStream sysout; // поток для вывода ошибок
	List<StringBuilder> allCode = new LinkedList<StringBuilder>();
	TokenStream tokens; // токены
	boolean error = false;
	int tabLevel = 0;
	boolean staticVariable = true;
	String className;

	public CodeGenerator(TokenStream tokens) {
		try {
			sysout = new PrintStream(System.err, true, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		this.tokens = tokens;
	}

	public void addCode(StringBuilder str, boolean nextLine) {
		if (nextLine) {
			addCode(str);
		} else {
			for (int i = 0; i < tabLevel; i++)
				str.insert(0, "\t");
			allCode.add(str);
		}
	}
	
	public void setClassName(String name){
		className = name;
	}

	public void addCode(StringBuilder str) {
		for (int i = 0; i < tabLevel; i++)
			str.insert(0, "\t");
		str.append('\n');
		allCode.add(str);
	}

	// разбор дерева
	public void handle(Tree tree) {

		switch (tree.getType()) {

		case 0: {
			for (int i = 0; i < tree.getChildCount(); i++) {
				handle(tree.getChild(i));
			}
			break;
		}
		case epigraphParser.INTEGER_NAME:
		case epigraphParser.BYTE_NAME:
		case epigraphParser.FLOAT_NAME:
		case epigraphParser.DOUBLE_NAME: {
			builder = new StringBuilder();
			if (staticVariable)
				handleVar(tree, "static " + tree.getText().toLowerCase());
			else
				handleVar(tree, tree.getText().toLowerCase());
			addCode(builder);
			break;
		}
		case epigraphParser.BEGIN: {
			tabLevel++;
			staticVariable = false;
			builder = new StringBuilder();
			handleMainBlock(tree);
			addCode(builder);
			// разбор всего, что лежит в main'е
			for (int i = 0; i < tree.getChildCount(); i++) {
				handle(tree.getChild(i));
			}
			break;
		}
		case epigraphParser.END: {
			addCode(new StringBuilder("System.in.read();\n}"));
			tabLevel--;
			staticVariable = true;
			break;
		}
		case epigraphParser.CONST: {
			for (int i = 0; i < tree.getChildCount(); i++) {
				builder = new StringBuilder();
				handleConst(tree);
				addCode(builder);
				break;
			}
			break;
		}
		case epigraphParser.ASSIGN: {
			String variableName;

			for (int i = 0; i < tree.getChildCount(); i++) {
				variableName = tree.getChild(i).getText();

				if (!table.contains(variableName))
					printErr(tree.getChild(i), Msg.variableNotDeclared);
			}
			addCode(new StringBuilder().append(tokens.toString(
					tree.getTokenStartIndex(), tree.getTokenStopIndex())));
			break;
		}
			/*
			 * case epigraphParser.STATEMENT: { String variableName;
			 * 
			 * for (int i = 0; i < tree.getChildCount(); i++) { variableName =
			 * tree.getChild(i).getText();
			 * 
			 * if (!table.contains(variableName)) printErr(tree.getChild(i),
			 * Msg.variableNotDeclared); } builder.append(
			 * tokens.toString(tree.getTokenStartIndex(),
			 * tree.getTokenStopIndex())).append("s\n"); break; }
			 */
			/*
			 * case epigraphParser.STATEMENT:{ addCode(new
			 * StringBuilder().append(
			 * tokens.toString(tree.getTokenStartIndex(),
			 * tree.getTokenStopIndex())).append('\n')); break; }
			 */
		case epigraphParser.EXPRESSION: {
			handleExpr(tree);
			break;
		}
		case epigraphParser.IDENTIFIER: {
			builder = new StringBuilder();
			handleMethod(tree);
			addCode(builder);
			// sysout.println(tree.getText());
			break;
		}
		case epigraphParser.IF: {
			addCode(new StringBuilder("if "), false);

			// разбираем expression
			handle(tree.getChild(0));

			addCode(new StringBuilder("{"));
			tabLevel++;

			// тело
			for (int i = 0; i < tree.getChild(1).getChildCount(); i++)
				handle(tree.getChild(1).getChild(i));

			tabLevel--;
			addCode(new StringBuilder("}"));

			// если есть ELSE, то разбираем его
			if (tree.getChildCount() > 2)
				handle(tree.getChild(2));

			break;
		}
		case epigraphParser.ELSE: {
			addCode(new StringBuilder("else"));
			// если след оператор IF
			boolean nextIf = false;
			if (tree.getChild(0).getType() == epigraphParser.IF) {
				nextIf = true;
			} else
				addCode(new StringBuilder("{"));

			for (int i = 0; i < tree.getChildCount(); i++) {
				handle(tree.getChild(i));
			}

			if (!nextIf) {
				addCode(new StringBuilder("}"));
			}
			break;
		}
			/*
			 * case epigraphParser.STATEMENT:{ String variableName;
			 * 
			 * for (int i = 0; i < tree.getChildCount(); i++) { variableName =
			 * tree.getChild(i).getText();
			 * 
			 * if (!table.contains(variableName)) printErr(tree.getChild(i),
			 * Msg.variableNotDeclared); } addCode(new StringBuilder().append(
			 * tokens.toString(tree.getTokenStartIndex(),
			 * tree.getTokenStopIndex())).append('\n')); break; }
			 */
		default: {
			System.err.println("DEFAULT: " + tree.getType() + " "
					+ tree.getLine() + ":" + tree.getCharPositionInLine());
		}

		}
	}

	void handleMethod(Tree tree) {
		if (tree.getText().equals("print") || tree.getText().equals("println")) {
			builder.append("System.out.").append(tree.getText()).append('(');
			builder.append(tree.getChild(0).getText());

			checkForVariable(tree.getChild(0));

			for (int i = 1; i < tree.getChildCount(); i++) {
				builder.append("+").append(tree.getChild(i).getText());
				checkForVariable(tree.getChild(i));
			}

			builder.append(");");
		} else if (tree.getText().equals("scan")) {
			{
				for (int i = 0; i < tree.getChildCount(); i++) {
					/*
					 * switch (tree.getChild(i).getType()) { case
					 * epigraphParser.INT: {
					 * builder.append(tree.getChild(i).getText()).append(
					 * " = sc.nextInt();"); } default: {
					 * printErr(tree.getChild(i), Msg.thisIsNotVariable); }
					 */
					if (tree.getChild(i).getType() == epigraphParser.IDENTIFIER) {
						checkForVariable(tree.getChild(i));
						builder.append(tree.getChild(i).getText()).append(
								" = sc.nextInt();");
					} else {
						printErr(tree.getChild(i), Msg.thisIsNotVariable);
					}
				}
			}
		}
	}

	/**
	 * 
	 * @param n
	 * @return возвращает true, если n является типом переменной (string, int и
	 *         тд)
	 */

	boolean isVariable(int n) {
		switch (n) {
		case epigraphParser.INT:
		case epigraphParser.STRING:
		case epigraphParser.FLOAT:
		case epigraphParser.CHAR: {
			return true;
		}
		default: {
			return false;
		}
		}
	}

	/**
	 * 
	 * @param tree
	 */

	void checkForVariable(Tree tree) {
		if (!table.contains(tree.getText()) && !isVariable(tree.getType()))
			printErr(tree, Msg.variableNotDeclared);
	}

	// разбор выражений
	private void handleExpr(Tree tree) {
		String variableName;

		for (int i = 0; i < tree.getChildCount(); i++) {
			variableName = tree.getChild(i).getText();

			if (!table.contains(variableName))
				printErr(tree.getChild(i), Msg.variableNotDeclared);
		}
		addCode(new StringBuilder().append(tokens.toString(
				tree.getTokenStartIndex(), tree.getTokenStopIndex())));
	}

	// разбор констант
	public void handleConst(Tree tree) {
		String name = tree.getChild(1).getText();
		String type = tree.getChild(0).getText().toLowerCase();
		builder.append("static final ").append(type).append(" ");
		builder.append(name).append("=").append(tree.getChild(1).getChild(0));

		if (!table.add(name, new Obj(Categories.constant)))
			printErr(tree.getChild(0), Msg.variableAlreadyExists);

		for (int i = 2; i < tree.getChildCount(); i++) {
			name = tree.getChild(i).getText();
			builder.append(",").append(name).append("=")
					.append(tree.getChild(i).getChild(0).getText());
			if (!table.add(name, new Obj(Categories.constant)))
				printErr(tree.getChild(i).getChild(0),
						Msg.variableAlreadyExists);
		}
		builder.append(";");
	}

	void printErr(Tree tree, String text) {
		sysout.println("Строка " + tree.getLine() + " позиция "
				+ tree.getCharPositionInLine() + " \'" + tree.getText() + "\' "
				+ text);
		error = true;
	}

	/*
	 * public void handleExpression(Tree tree) {
	 * //System.out.println("Handle expression");
	 * 
	 * int N = tree.getChildCount();
	 * 
	 * //reduce(tree); //System.out.println("[expression] N: " + N + ", node: "
	 * + tree);
	 * 
	 * if (tree.getType() == epigraphParser.FUNCTION_DECLARATOR) {
	 * builder.append(tree.getChild(0)).append(" ( "); for (int i = 1; i <
	 * tree.getChildCount(); i++) { handleExpression(tree.getChild(i)); }
	 * builder.append(") "); } else if (tree.getType() ==
	 * epigraphParser.BRACKETS) { builder.append("( ");
	 * handleExpression(tree.getChild(0)); builder.append(") "); } else if
	 * ("^".equals(tree.getText())) { builder.append("Math.pow( ");
	 * handleExpression(tree.getChild(0)); builder.append(", ");
	 * handleExpression(tree.getChild(1)); builder.append(") ");
	 * 
	 * 
	 * } else if (N == 0) { builder.append(tree).append(" "); } else if (N == 1)
	 * { builder.append(tree); handleExpression(tree.getChild(0)); } else if (N
	 * == 2) { handleExpression(tree.getChild(0));
	 * builder.append(tree).append(" "); handleExpression(tree.getChild(1)); } }
	 */

	public void handleMainBlock(Tree tree) {
		builder.append("public static void main(String[] args) throws IOException {\n");
		builder.append("java.util.Scanner sc = new java.util.Scanner(System.in);\n");
	}

	public void handleVar(Tree tree, String type) {
		String name = tree.getChild(0).getText();
		builder.append(type).append(" ").append(name);

		// проверяем задано ли значение переменной по умолчанию
		if (tree.getChild(0).getChildCount() != 0)
			builder.append("=").append(tree.getChild(0).getChild(0));
		else
			builder.append("=0");

		if (!table.add(name, new Obj(Categories.variable)))
			printErr(tree.getChild(0), Msg.variableAlreadyExists);

		for (int i = 1; i < tree.getChildCount(); i++) {
			name = tree.getChild(i).getText();
			builder.append(",").append(name);

			if (tree.getChild(i).getChildCount() != 0)
				builder.append("=").append(tree.getChild(i).getChild(0));
			else
				builder.append("=0");

			if (!table.add(name, new Obj(Categories.variable)))
				printErr(tree.getChild(i), Msg.variableAlreadyExists);
		}
		builder.append(";");
	}

	/*
	 * public void handleFunction(Tree tree) {
	 * //System.out.println("Handle function: " + tree.getText());
	 * 
	 * String name = tree.getChild(0).getText(); String type =
	 * tree.getChild(1).getText();
	 * 
	 * builder.append(type).append(" "); builder.append(name).append("( ");
	 * 
	 * handleArgumentList(tree.getChild(2)); builder.append(") { return "); //
	 * handleExpression(tree.getChild(3).getChild(0));
	 * 
	 * builder.append("; } "); }
	 * 
	 * public void handleArgumentList(Tree tree) {
	 * 
	 * if (0 < tree.getChildCount()) { handleArgument(tree.getChild(0)); for
	 * (int i = 1; i < tree.getChildCount(); i++) { builder.append(", ");
	 * handleArgument(tree.getChild(i)); } } }
	 * 
	 * public void handleArgument(Tree tree) {
	 * //System.out.println("Handle argument: " + tree.getText() + " childs: " +
	 * tree.getChildCount()); String name = tree.getChild(0).getText(); String
	 * type = tree.getChild(1).getText(); builder.append(type).append(" ");
	 * builder.append(name).append(" ");
	 * 
	 * }
	 */

	public StringBuilder getSource() {
		StringBuilder res = new StringBuilder();
	//res.append("package TEST;\n");
		
		res.append("import java.io.IOException;\n");

		res.append("public class " + className + "{\n\n");

		// for (StringBuilder variable : variables) {
		// res.append(variable);
		// }

		// res.append(mainBlock);

		// allCode.add(builder);

		for (StringBuilder code : allCode) {
			res.append(code);
		}

		// for (StringBuilder function : functions) {
		// res.append("  static ").append(function).append("\n\n");
		// }

		/*
		 * res.append("  public static void main(String args[]){\n"); for
		 * (StringBuilder expr : expressions) { //
		 * System.out.println("[code] expr: '" + expr + "'");
		 * res.append("    System.out.println( \""); res.append(expr);
		 * res.append(" = \" + ( "); res.append(expr); res.append(") );\n"); }
		 * res.append("  }\n");
		 */
		res.append("}");
		return res;
	}
}
